fix(backend): 헬스체크에서 RabbitMQ 상태가 항상 UNKNOWN 이던 문제 - #183
Merged
Conversation
배포된 dev 의 `/api/system/health` 를 실제로 찔러 보다 발견했다. database 만 UP 이고 rabbitmq 는 details 까지 빈 채 UNKNOWN 이었다. `SystemHealthService` 는 Actuator 컴포넌트를 이름으로 조회하는데, RabbitMQ 를 `"rabbitmq"` 로 찾고 있었다. Spring 은 `rabbitHealthContributor` 빈을 등록하므로 실제 키는 **`rabbit`** 이다(빈 이름에서 접미사를 뗀 값). 없는 이름이라 `healthForPath` 가 null 을 돌려주고 그대로 UNKNOWN 이 된다 — 예외가 아니라 조용한 무응답이라 눈에 띄지 않았다. 바로 옆의 `database` 는 `"db"` 로 올바르게 매핑돼 있었다. 응답 키와 Actuator 키를 분리한 `ComponentSpec(name, actuatorPath)` 구조는 이미 맞았고, rabbit 값만 틀렸다. 영향: `/api/system/ready`(database·rabbitmq 종합)가 **항상 UNKNOWN** 이라 readiness 판단에 쓸 수 없었다. RabbitMQ 가 죽으면 질문 생성·피드백·음성 분석이 전부 멎는데 이 엔드포인트는 아무것도 알려주지 않는다. (컨테이너 healthcheck 는 Spring 자체 `/actuator/health` 를 쓰므로 배포 게이트 자체는 영향 없었다.) ## 테스트가 왜 못 잡았나 픽스처를 `"rabbitmq"` 키로 등록해서 **같은 실수를 그대로 재현**하고 있었다. 실제 Actuator 키로 바꾸고, 매핑 자체를 못 박는 테스트 2개를 추가했다 — 응답 키는 rabbitmq 로 유지하면서 Actuator 는 rabbit 에서 읽는지, 그리고 없는 이름일 때 UNKNOWN 이 되는지. `docs/observability.md` 에 키가 다르다는 점과 s3·aiServer 미구현으로 `/health` 종합 status 가 UNKNOWN 으로 고정된다는 점을 적었다.
This was referenced Aug 19, 2026
i3months
added a commit
to i3months/stackup
that referenced
this pull request
Aug 28, 2026
…ness 로 Team-StackUp#183 에서 남겨둔 부분. `/api/system/health` 의 종합 status 가 s3·aiServer 미구현 때문에 UNKNOWN 으로 고정돼 있어 엔드포인트를 쓸 수 없었다. ## indicator 두 개 - `S3HealthIndicator` — `headBucket` 으로 엔드포인트·자격증명·버킷을 한 번에 확인. 스토리지가 죽으면 이력서 업로드·음성 답변·TTS 재생이 전부 실패한다. `ObjectStorageClient.verifyAvailable()` 을 추가했다(get/put 은 대상 키가 필요해서 헬스체크에 쓸 수 없다). - `AiServerHealthIndicator` — **작업 큐의 컨슈머 수**로 판단한다. Core 는 AI 를 HTTP 로 호출하지 않으므로(아키텍처 §4.1: RabbitMQ 전용) 헬스체크 하나 때문에 Core→AI HTTP 의존을 새로 만들지 않았다. 컨슈머 수가 오히려 정확한 신호다 — 프로세스 생존보다 **큐를 실제로 구독 중인가**가 중요하고, 컨슈머 0 이면 질문 생성·꼬리질문·피드백이 쌓이기만 한다. 브로커 자체가 죽은 경우는 UNKNOWN 으로 둔다(그건 rabbitmq 컴포넌트가 알려주므로 DOWN 을 겹쳐 내면 "AI 가 죽었다" 로 오독된다). 빈 이름이 곧 Actuator 키가 되도록 맞췄다(`s3HealthIndicator`→`s3`) — Team-StackUp#183 과 같은 함정. ## 컨테이너 healthcheck 를 종합 → readiness 로 indicator 를 추가하면 종합(`/actuator/health`)에 s3·aiServer 가 들어가는데, 컨테이너 healthcheck 가 그 종합을 보고 있었다. 그대로 두면 **AI 가 죽었다고 백엔드 컨테이너가 unhealthy 가 되어** 정작 멀쩡한 로그인·히스토리까지 rotation 에서 빠진다. healthcheck 를 `/actuator/health/readiness` 로 바꾸고 readiness 그룹을 `readinessState + db + rabbit` 으로 **명시**했다. 그냥 liveness 로 바꾸면 DB 장애도 배포 게이트를 통과해 버리므로, 백엔드가 자기 일을 하려면 반드시 필요한 것만 남겼다. 그룹 멤버십 검증은 기본값(엄격)을 유지한다 — 이름을 틀리면 부팅이 실패해 배포에서 잡힌다. 조용히 UNKNOWN 이 되는 것보다 낫다(Team-StackUp#183 이 정확히 그 실패였다). 테스트 프로파일은 DataSource 를 제외하므로 거기서만 그룹을 축소했다. ## 테스트 - `AiServerHealthIndicatorTest` (4) — 컨슈머 있음 UP, 0이면 DOWN(쌓인 메시지 수 포함), 큐 없음 DOWN, 브로커 불가 UNKNOWN - `S3HealthIndicatorTest` (2) — 도달 가능 UP, 실패 시 DOWN + 사유
i3months
added a commit
to i3months/stackup
that referenced
this pull request
Aug 28, 2026
마지막 점검에서 나왔다. `/api/system/*` 는 permitAll 인데 컴포넌트 상세를 그대로 담고 있었다:
"rabbitmq": { "version": "4.3.5" }
"s3": { "bucket": "stackup" }
"aiServer": { "queue": "ai.generate.questions", "consumers": 1, "pendingMessages": 0 }
Actuator 는 기본값이 `show-details: never` 다. 그런데 `SystemHealthService` 가 descriptor 에서
상세를 직접 꺼내 자기 응답에 담으면서 **그 보호를 우회**하고 있었다. RabbitMQ 버전은 알려진
CVE 를 겨냥하는 데 쓰이고, 버킷명·큐 이름·적체량은 내부 토폴로지와 부하를 그대로 드러낸다.
`rabbitmq` 상세는 Team-StackUp#183(키 오타 수정)으로, `s3`·`aiServer` 상세는 Team-StackUp#184(indicator 구현)로
오늘 내가 늘린 것이다 — 늘린 김에 닫는다.
`ComponentHealthResponse` 에서 `details` 를 제거해 **이름·상태만** 담는다. 프로브 용도에는
그것으로 충분하고, 상세가 필요하면 호스트에서 Spring 자체 `/actuator/health` 를 본다
(nginx 가 외부로 라우팅하지 않는 것을 확인했다 — 공개 URL 로는 SPA HTML 이 돌아온다).
반사(reflection)로 상세를 꺼내던 `extractDetails` 도 함께 사라진다.
프론트·realtime 어디서도 이 엔드포인트를 호출하지 않아 소비자 영향은 없다.
테스트: `health_doesNotExposeComponentDetails` — 상태는 전달되지만 응답 타입에 상세 필드가
아예 없다는 것을 record 컴포넌트로 못 박는다.
## 함께: frontend/.env.example 의 잘못된 호스트
`VITE_API_BASE_URL`·`VITE_SSE_BASE_URL` 가 `https://www.udangtang.site` 를 가리키고 있었다.
실제 배포 호스트는 `https://stack-up.shop` 다(deploy-app.yml 이 프론트 빌드에 주입하는 값).
새로 온 사람이 그대로 복사하면 존재하지 않는 백엔드를 보게 된다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
어떻게 찾았나
배포된 dev(
https://stack-up.shop)의/api/system/health를 실제로 찔러 봤다.{ "status": "UNKNOWN", "components": { "database": { "status": "UP", "details": { "database": "PostgreSQL", ... } }, "rabbitmq": { "status": "UNKNOWN", "details": {} }, "s3": { "status": "UNKNOWN", "details": {} }, "aiServer": { "status": "UNKNOWN", "details": {} } } }database 만 UP 이고 rabbitmq 는 details 까지 비어 있었다.
원인
SystemHealthService는 Actuator 컴포넌트를 이름으로 조회한다:Spring 은
rabbitHealthContributor빈을 등록하므로 Actuator 키는rabbit이다(빈 이름에서 접미사를 뗀 값).RabbitHealthContributorAutoConfiguration소스로 확인했다.없는 이름이라
healthEndpoint.healthForPath("rabbitmq")가 null 을 돌려주고 그대로 UNKNOWN 이 된다 — 예외가 아니라 조용한 무응답이라 눈에 띄지 않았다.바로 옆의
database는"db"로 올바르게 매핑돼 있다. 응답 키와 Actuator 키를 분리한ComponentSpec(name, actuatorPath)구조 자체는 맞았고, rabbit 값만 틀렸다. 공개 응답 키는rabbitmq그대로 유지된다.영향
/api/system/ready는 database·rabbitmq 를 종합하는데, rabbitmq 가 영구 UNKNOWN 이라 readiness 가 항상 UNKNOWN 이었다. readiness 판단에 쓸 수 없는 상태다.RabbitMQ 가 죽으면 질문 생성·꼬리질문·피드백·음성 분석이 전부 멎는다. 제품이 사실상 죽는데 이 엔드포인트는 아무것도 알려주지 않았다.
컨테이너 healthcheck 는 Spring 자체
/actuator/health(Actuator 종합)를 쓰므로 배포 게이트 자체는 영향이 없었다 — RabbitMQ 장애 시 배포는 정상적으로 실패한다. 문제는 운영 중 모니터링용 공개 API 쪽이다.테스트가 왜 못 잡았나
픽스처를
"rabbitmq"키로 레지스트리에 등록해서 같은 실수를 그대로 재현하고 있었다. 프로덕션에서 Spring 이"rabbit"으로 등록한다는 사실이 테스트에 반영된 적이 없다.rabbit)로 교정health_readsRabbitFromActuatorKeyNotResponseKey— 응답 키는 rabbitmq 로 유지하면서 Actuator 는 rabbit 에서 읽는지, details 까지 전달되는지health_reportsUnknownWhenActuatorHasNoSuchComponent— 없는 이름일 때의 동작을 분리해 고정남는 것 (이 PR 범위 밖)
s3·aiServer는 커스텀 indicator 가 아직 없어 UNKNOWN 이고, 그래서/health의 종합 status 는 여전히 UNKNOWN 으로 고정된다(기존 테스트가 이 상태를 의도로 명시하고 있다). 이 PR 로/ready(database·rabbitmq)는 정상 동작하게 된다.두 indicator 를 실제로 구현하려면 S3
headBucket, AI 서버GET /health호출이 필요하다 — 별도 작업으로 두는 게 맞다고 봤다.docs/observability.md에 현재 상태를 적어뒀다.영향 범위
rabbitmq유지, 값만 실제 상태로)리뷰어 체크포인트
/health종합 status 를 쓸 수 있게 하려면 s3·aiServer indicator 구현이 필요하다. 우선순위 판단 부탁